package gh625simpleviewer;

import java.io.*;
import java.util.ArrayList;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import jssc.SerialPort;
import jssc.SerialPortException;

public class Vars {

    /*
     * Options ComPortName - name of the communications port. E.g. COM3 or /dev/ttyUSB0. UseProxy ProxyHost ProxyPort
     */
    public static Properties opts = new Properties();
    public static String OptsFileName = "";

    public static void SaveOptions() {
        try {
            opts.storeToXML(new FileOutputStream(OptsFileName, false), null);
        } catch (IOException ex) {
            System.out.println("Vars.SaveOptions(): " + ex.toString());
        }
    }

    public static void LoadOptions() {
        try {
            opts.loadFromXML(new FileInputStream(OptsFileName));
        } catch (IOException ex) {
            System.out.println("Vars.LoadOptions(): " + ex.toString());
        }
    }
    /*
     * Tracks
     */
    public static SortedMap<Integer, Track> tempTrackList = new TreeMap<>();
    public static ArrayList<OneDay> days = new ArrayList<>();

    public static byte[] sendCMDgetResponse(byte[] cmd) {
        SerialPort serialPort = new SerialPort(Vars.opts.getProperty("ComPortName"));

        byte[] resp;

        try {
            if (!serialPort.isOpened()) {
                if (!serialPort.openPort()) {
                    return null;
                }
                serialPort.setParams(jssc.SerialPort.BAUDRATE_57600, jssc.SerialPort.DATABITS_8, jssc.SerialPort.STOPBITS_1, jssc.SerialPort.PARITY_NONE);
                if (!serialPort.isOpened()) {
                    return null;
                }
            }
            serialPort.writeBytes(cmd);
            Thread.sleep(1500);
            resp = serialPort.readBytes();
            serialPort.closePort();
        } catch (SerialPortException | InterruptedException ex) {
            return null;
        }

        return resp;
    }

    public static void getDeviceInfo() {
        byte[] command_b = new byte[5];
        command_b[0] = (byte) 0x02;
        command_b[1] = (byte) 0x00;
        command_b[2] = (byte) 0x01;
        command_b[3] = (byte) 0x85;
        command_b[4] = (byte) 0x84;

        byte[] info = sendCMDgetResponse(command_b);

        if (info == null || info.length == 0 || info[0] == 0) {
            return;
        }

        int c;

        // Device name
        c = 3;
        DeviceInfo.Device = "";
        while (info[c] != 0) {
            DeviceInfo.Device += (char) info[c];
            c++;
        }

        // User Name
        c = 45;
        DeviceInfo.Name = "";
        while (info[c] != 0) {
            DeviceInfo.Name += (char) info[c];
            c++;
        }

        // User sex
        if (info[56] == 0) {
            DeviceInfo.Sex = "Male";
        } else {
            DeviceInfo.Sex = "Female";
        }

        // User age
        DeviceInfo.Age = uByteToInt(info[57]);

        // User weight & height
        DeviceInfo.Weight_pounds = uByteToInt(info[59]);
        DeviceInfo.Weight_kg = uByteToInt(info[61]);
        DeviceInfo.Height_inch = uByteToInt(info[63]);
        DeviceInfo.Height_cm = Integer.valueOf(uByteToInt(info[65]));

        // Tracks count
        DeviceInfo.TracksCount = uByteToInt(info[67]);

        // Birth date
        DeviceInfo.Year = uByteToInt(info[69]) * 256 + uByteToInt(info[70]);
        DeviceInfo.Month = uByteToInt(info[71]);
        DeviceInfo.Day = uByteToInt(info[72]);
    }

    public static void getTracksInfo() {
        byte[] command_b = new byte[5];
        command_b[0] = (byte) 0x02;
        command_b[1] = (byte) 0x00;
        command_b[2] = (byte) 0x01;
        command_b[3] = (byte) 0x78;
        command_b[4] = (byte) 0x79;

        byte[] info = sendCMDgetResponse(command_b);

        if (info == null || info.length == 0 || info[0] == 0) {
            return;
        }

        tempTrackList.clear();

        int trcount = 0;
        int c;
        while (trcount < DeviceInfo.TracksCount) {
            Track ti = new Track();

            c = 3 + trcount * 31;

            ti.Year = uByteToInt(info[c]) + 2000;
            c++;
            ti.Month = uByteToInt(info[c]);
            c++;
            ti.Day = uByteToInt(info[c]);
            c++;
            ti.Hour = uByteToInt(info[c]);
            c++;
            ti.Min = uByteToInt(info[c]);
            c++;
            ti.Sec = uByteToInt(info[c]);
            c++;

            ti.Laps = uByteToInt(info[c]);
            c++;

            ti.Duration_msec = uByteToInt(info[c]) * 16777216
                    + uByteToInt(info[c + 1]) * 65536
                    + uByteToInt(info[c + 2]) * 256
                    + uByteToInt(info[c + 3]);
            c++;
            c++;
            c++;
            c++;
            ti.Duration_h = (ti.Duration_msec / 36000);
            ti.Duration_m = (ti.Duration_msec - ti.Duration_h * 36000) / 600;
            ti.Duration_s = (ti.Duration_msec - ti.Duration_h * 36000 - ti.Duration_m * 600) / 10;

            ti.Duration_msec = ti.Duration_msec * 10;

            ti.Distance =
                    uByteToInt(info[c]) * 16777216
                    + uByteToInt(info[c + 1]) * 65536
                    + uByteToInt(info[c + 2]) * 256
                    + uByteToInt(info[c + 3]);
            c++;
            c++;
            c++;
            c++;

            ti.AvgSpeed = (double) (3600 * ti.Distance / ti.Duration_msec) / 100;

            ti.Calories = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.TopSpeed = (double) (uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1])) / 100;
            c++;
            c++;

            ti.TopHeartBeat = uByteToInt(info[c]);
            c++;

            ti.AvgHeartBeat = uByteToInt(info[c]);
            c++;

            ti.AscHeight = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.DescHeight = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.Points = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);
            c++;
            c++;

            ti.ID = uByteToInt(info[c]) * 256 + uByteToInt(info[c + 1]);

            tempTrackList.put(ti.ID, ti);
            trcount++;
        }
    }

    public static void getTrack(Integer TrackID) {
        byte[] command_b = new byte[5];
        byte[] command_b_t = new byte[9];

        command_b_t[0] = (byte) 0x02;
        command_b_t[1] = (byte) 0x00;
        command_b_t[2] = (byte) 0x05;
        command_b_t[3] = (byte) 0x80;
        command_b_t[4] = (byte) 0x00;
        command_b_t[5] = (byte) 0x01;
        command_b_t[6] = (byte) (TrackID / 256);
        command_b_t[7] = (byte) (TrackID - command_b_t[6]);

        byte checksum = (byte) 0x00;
        checksum ^= command_b_t[2];
        checksum ^= command_b_t[3];
        checksum ^= command_b_t[4];
        checksum ^= command_b_t[5];
        checksum ^= command_b_t[6];
        checksum ^= command_b_t[7];
        command_b_t[8] = checksum;

        byte[] tseg = sendCMDgetResponse(command_b_t);

        int SegCounter = 0;
        while (SegCounter < 500) {
            int sp = 30;

            Integer trg = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);

            if (trg.equals(0xffff)) {
                sp = 34;
                while (sp < 2057) {
                    TrackLap tl = new TrackLap();

                    tl.Stop =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tl.Duration =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tl.Distance =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tl.Calories = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                    sp++;
                    sp++;

                    tl.MaxSpeed = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                    sp++;
                    sp++;

                    tl.MaxHR = uByteToInt(tseg[sp]);
                    sp++;
                    tl.AvgHR = uByteToInt(tseg[sp]);
                    sp++;

                    tl.Points =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tempTrackList.get(TrackID).tls.add(tl);

                    if (tempTrackList.get(TrackID).Laps.equals(tempTrackList.get(TrackID).tls.size())) {
                        sp = 2070;
                    }
                }
            } else {
                sp = 34;
                while (sp < 2074) {
                    TrackPoint tp = new TrackPoint();
                    tp.Lat =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tp.Lon =
                            uByteToInt(tseg[sp + 0]) * 16777216
                            + uByteToInt(tseg[sp + 1]) * 65536
                            + uByteToInt(tseg[sp + 2]) * 256
                            + uByteToInt(tseg[sp + 3]);
                    sp++;
                    sp++;
                    sp++;
                    sp++;

                    tp.Alt = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                    sp++;
                    sp++;

                    tp.Speed = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                    sp++;
                    sp++;

                    tp.HeartRate = uByteToInt(tseg[sp]);
                    sp++;

                    tp.TimeShift = uByteToInt(tseg[sp]) * 256 + uByteToInt(tseg[sp + 1]);
                    sp++;
                    sp++;

                    tempTrackList.get(TrackID).tps.add(tp);

                    if (tempTrackList.get(TrackID).Points.equals(tempTrackList.get(TrackID).tps.size())) {
                        sp = 2075;
                    }
                }
            }
            //
            SegCounter++;
            command_b[0] = (byte) 0x02;
            command_b[1] = (byte) 0x00;
            command_b[2] = (byte) 0x01;
            command_b[3] = (byte) 0x81;
            command_b[4] = (byte) 0x80;
            tseg = sendCMDgetResponse(command_b);
            if (tseg[0] == (byte) 0x8A) {
                SegCounter = 501;
            }
        }
    }

    public static void SaveTrack(Track track) {
        String TrackName = track.Year.toString() + "-"
                + track.Month.toString() + "-"
                + track.Day.toString() + "_"
                + track.Hour.toString() + "-"
                + track.Min.toString() + "-"
                + track.Sec.toString() + ".track";
        String TrackFileName = System.getProperty("user.home")
                + "/.gh625simpleviewer/" + TrackName;
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(
                    new OutputStreamWriter(
                    new FileOutputStream(TrackFileName), "UTF-8"));
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }

        try {
            bw.write("Track - "
                    + track.Year.toString() + "-"
                    + track.Month.toString() + "-"
                    + track.Day.toString() + " "
                    + track.Hour.toString() + ":"
                    + track.Min.toString() + ":"
                    + track.Sec.toString());
            bw.newLine();
            bw.write("===============================");
            bw.newLine();

            bw.write("track:");
            bw.newLine();
            bw.write(track.Year.toString());
            bw.newLine();
            bw.write(track.Month.toString());
            bw.newLine();
            bw.write(track.Day.toString());
            bw.newLine();
            bw.write(track.Hour.toString());
            bw.newLine();
            bw.write(track.Min.toString());
            bw.newLine();
            bw.write(track.Sec.toString());
            bw.newLine();
            bw.write(track.Laps.toString());
            bw.newLine();
            bw.write(track.Duration_msec.toString());
            bw.newLine();
            bw.write(track.Duration_h.toString());
            bw.newLine();
            bw.write(track.Duration_m.toString());
            bw.newLine();
            bw.write(track.Duration_s.toString());
            bw.newLine();
            bw.write(track.Distance.toString());
            bw.newLine();
            bw.write(track.Calories.toString());
            bw.newLine();
            bw.write(track.TopSpeed.toString());
            bw.newLine();
            bw.write(track.TopHeartBeat.toString());
            bw.newLine();
            bw.write(track.AvgHeartBeat.toString());
            bw.newLine();
            bw.write(track.AscHeight.toString());
            bw.newLine();
            bw.write(track.DescHeight.toString());
            bw.newLine();
            bw.write(track.Points.toString());
            bw.newLine();
            bw.write("===============================");
            bw.newLine();

            for (TrackLap tl : track.tls) {
                bw.write("lap:");
                bw.newLine();
                bw.write(tl.Stop.toString());
                bw.newLine();
                bw.write(tl.Duration.toString());
                bw.newLine();
                bw.write(tl.Distance.toString());
                bw.newLine();
                bw.write(tl.Calories.toString());
                bw.newLine();
                bw.write(tl.MaxSpeed.toString());
                bw.newLine();
                bw.write(tl.MaxHR.toString());
                bw.newLine();
                bw.write(tl.AvgHR.toString());
                bw.newLine();
                bw.write(tl.Points.toString());
                bw.newLine();
                bw.write("===============================");
                bw.newLine();
            }

            for (TrackPoint tp : track.tps) {
                bw.write("point:");
                bw.newLine();
                bw.write(tp.Lat.toString());
                bw.newLine();
                bw.write(tp.Lon.toString());
                bw.newLine();
                bw.write(tp.Alt.toString());
                bw.newLine();
                bw.write(tp.Speed.toString());
                bw.newLine();
                bw.write(tp.HeartRate.toString());
                bw.newLine();
                bw.write(tp.TimeShift.toString());
                bw.newLine();
                bw.write("===============================");
                bw.newLine();
            }

            bw.close();
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }
    }

    public static void LoadTracks() {
        tempTrackList.clear();
        days.clear();
        File dir = new File(System.getProperty("user.home") + "/.gh625simpleviewer/");

        ArrayList<String> fnames = new ArrayList<>();
        for (String filen : dir.list()) {
            if (filen.endsWith(".track")) {
                fnames.add(filen);
            }
        }
        java.util.Collections.sort(fnames);

        int i = 0;
        for (String filen : fnames) {
            tempTrackList.put(i, LoadTrack(System.getProperty("user.home") + "/.gh625simpleviewer/" + filen));
            i++;
        }

        for (Track trk : tempTrackList.values()) {
            OneDay od = getOneDayForTrack(trk);
            if (od == null) {
                od = new OneDay();
                od.Year = trk.Year;
                od.Month = trk.Month;
                od.Day = trk.Day;
                od.Duration_msec = trk.Duration_msec;
                od.Calories = trk.Calories;
                od.Distance = trk.Distance;
                od.DayTracks.add(trk);
                days.add(od);
            } else {
                od.Duration_msec = od.Duration_msec + trk.Duration_msec;
                od.Calories = od.Calories + trk.Calories;
                od.Distance = od.Distance + trk.Distance;
                od.DayTracks.add(trk);
            }
        }

        java.util.Collections.sort(days);
    }

    private static OneDay getOneDayForTrack(Track trk) {
        for (OneDay od : days) {
            if (od.Year.equals(trk.Year) && od.Month.equals(trk.Month) && od.Day.equals(trk.Day)) {
                return od;
            }
        }
        return null;
    }

    private static Track LoadTrack(String filename) {
        Track trk = new Track();

        BufferedReader br = null;
        try {
            br = new BufferedReader(
                    new InputStreamReader(
                    new FileInputStream(filename), "UTF-8"));
        } catch (UnsupportedEncodingException | FileNotFoundException ex) {
            System.out.println(ex.toString());
        }
        if (br == null) {
            return null;
        }

        try {
            br.readLine();
            br.readLine();
            br.readLine();

            trk.Year = new Integer(br.readLine());
            trk.Month = new Integer(br.readLine());
            trk.Day = new Integer(br.readLine());
            trk.Hour = new Integer(br.readLine());
            trk.Min = new Integer(br.readLine());
            trk.Sec = new Integer(br.readLine());

            trk.Laps = new Integer(br.readLine());

            trk.Duration_msec = new Integer(br.readLine());
            trk.Duration_h = new Integer(br.readLine());
            trk.Duration_m = new Integer(br.readLine());
            trk.Duration_s = new Integer(br.readLine());

            trk.Distance = new Integer(br.readLine());
            trk.Calories = new Integer(br.readLine());

            trk.TopSpeed = new Double(br.readLine());
            trk.AvgSpeed = (double) (3600 * trk.Distance / trk.Duration_msec) / 10;

            trk.TopHeartBeat = new Integer(br.readLine());
            trk.AvgHeartBeat = new Integer(br.readLine());

            trk.AscHeight = new Integer(br.readLine());
            trk.DescHeight = new Integer(br.readLine());

            trk.Points = new Integer(br.readLine());

            String line;
            br.readLine();
            while ((line = br.readLine()) != null) {
                if (line.equals("lap:")) {
                    TrackLap tl = new TrackLap();
                    tl.Stop = new Integer(br.readLine());
                    tl.Duration = new Integer(br.readLine());
                    tl.Distance = new Integer(br.readLine());
                    tl.Calories = new Integer(br.readLine());
                    tl.MaxSpeed = new Integer(br.readLine());
                    tl.MaxHR = new Integer(br.readLine());
                    tl.AvgHR = new Integer(br.readLine());
                    tl.Points = new Integer(br.readLine());
                    trk.tls.add(tl);
                    br.readLine();
                    continue;
                }
                if (line.equals("point:")) {
                    TrackPoint tp = new TrackPoint();
                    tp.Lat = new Integer(br.readLine());
                    tp.Lon = new Integer(br.readLine());
                    tp.Alt = new Integer(br.readLine());
                    tp.Speed = new Integer(br.readLine());
                    tp.HeartRate = new Integer(br.readLine());
                    tp.TimeShift = new Integer(br.readLine());
                    trk.tps.add(tp);
                    br.readLine();
                    continue;
                }
            }

            br.close();

        } catch (IOException ex) {
            System.out.println(ex.toString());
        }

        return trk;
    }

    public static void ExportTrackToKML(Track trk, String filename) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(
                    new OutputStreamWriter(
                    new FileOutputStream(filename), "UTF-8"));
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }

        try {
            bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            bw.newLine();
            bw.write("<kml xmlns=\"http://www.opengis.net/kml/2.2\">");
            bw.newLine();
            bw.write("  <Document>");
            bw.newLine();
            bw.write("    <name>GPS Data " + trk.toString() + "</name>");
            bw.newLine();
            bw.write("    <description>"
                    + "Duration - " + trk.getTrackDurationString() + "\n"
                    + "Distance - " + (double) trk.Distance / 1000 + "\n"
                    + "Speed - " + trk.AvgSpeed + " (top speed - " + trk.TopSpeed + ")" + "\n"
                    + "</description>");
            bw.newLine();
            bw.write("    <Placemark>");
            bw.newLine();
            bw.write("      <Style>id=\"linestyle\"");
            bw.newLine();
            bw.write("        <LineStyle>");
            bw.newLine();
            bw.write("          <color>7f0000ff</color>");
            bw.newLine();
            bw.write("          <width>4</width>");
            bw.newLine();
            bw.write("        </LineStyle>");
            bw.newLine();
            bw.write("      </Style>");
            bw.newLine();
            bw.write("      <name>Path</name>");
            bw.newLine();
            bw.write("      <styleUrl>#lineStyle</styleUrl>");
            bw.newLine();
            bw.write("      <LineString>");
            bw.newLine();
            bw.write("        <extrude>1</extrude>");
            bw.newLine();
            bw.write("        <tessellate>1</tessellate>");
            bw.newLine();
            bw.write("        <coordinates>");
            bw.newLine();

            for (TrackPoint tp : trk.tps) {
                Double lon = (double) tp.Lon / 1000000;
                Double lat = (double) tp.Lat / 1000000;
                String pointstring = "          "
                        + lon.toString() + ", "
                        + lat.toString() + ", "
                        + tp.Alt.toString();
                bw.write(pointstring);
                bw.newLine();
            }

            bw.write("        </coordinates>");
            bw.newLine();
            bw.write("      </LineString>");
            bw.newLine();
            bw.write("    </Placemark>");
            bw.newLine();
            bw.write("  </Document>");
            bw.newLine();
            bw.write("</kml>");

            bw.close();
        } catch (IOException ex) {
            System.out.println(ex.toString());
        }
    }

    public static void ExportTrackToGPX(Track trk, String filename) {
    }
    /*
     * Other
     */
    public static String Version = "GH-625 Simple Viewer 0.10-00";

    private static int uByteToInt(byte b) {
        return (int) b & 0xFF;
    }
}
